package com.rosca.converters.number2word.impl;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;

/**
 * User: vrosca
 * Date: 7/27/11
 * Time: 11:22 AM
 * &copy; PFA Rosca Val Dan Dar Ion 2011
 * <p>
 * Implementation of the required methods for English number spelling.
 * Subclasses can adapt the behavior to cater for variations between
 * American, British and other English variants (Template method pattern)
 * </p>
 */
public class ENConverter extends Arabic3DigitGroupConverter {
    /**
     * map of English cardinal numbers - kept static for sharing between instances
     * as cost of reading from properties file might add up
     */
    private static final Map<Integer, String> cardinals = new HashMap<Integer, String>();
    /**
     * names of groups corresponding to a groupIdx (0 - units, 1 - thousands, 2 - millions)
     */
    final String[] groups = {"", spell(Keyword.THOUSAND), spell(Keyword.MILLION)};

    /**
     * Specifies if hundreds should be separated from
     * unites and tens by "and" (eg one hundred and two)
     * Subclasses override this to adapt behavior
     *
     * @return truth value
     */
    public boolean useHundredsAnd() {
        return false;
    }

    /**
     * Specifies what separator should be used between
     * units and tens (eg. ninety-five or ninety five).
     * Subclasses override this to adapt behavior
     *
     * @return separator
     */
    public String unitTensSeparator() {
        return " ";
    }

    /**
     * Load cardinal number list statically for sharing between instances
     * as cost of reading from properties adds up if performed
     * every time a converter is constructed
     *
     * @throws IOException if the cardinal numbers file can't be found
     */
    private synchronized static void loadCardinals() throws IOException {
        if (cardinals.isEmpty()) {
            Properties properties = new Properties();
            InputStream stream = ENConverter.class.getResourceAsStream("ENConverter.properties");
            if (stream == null)
                throw new IOException("ENConverter.properties not found");
            properties.load(stream);
            for (String cardinal : properties.stringPropertyNames())
                cardinals.put(Integer.parseInt(cardinal), properties.getProperty(cardinal));
        }
    }

    @Override
    String spell(Keyword keyword) {
        return keyword.toString().toLowerCase();
    }

    /**
     * Spells a 3-digit group onto the spelling stack
     *
     * @param n3            current 3-digit group
     * @param groupIdx      index of group (0 - units, 1 - thousands, 2 - millions)
     * @param spelled       part of whole number that was already spelled
     * @param spellingStack stack containing spelled words
     */
    @Override
    void spellN3(int n3, int groupIdx, int spelled, Stack<String> spellingStack) {
        if (n3 < 0 || n3 >= 1000)
            throw new IllegalArgumentException("The number must belong to the 0 .. 999 interval");
        if (n3 == 0)
            return; // do not spell 0 - would lead to misspellings such as "1,000,000 = one million zero thousand zero"
        int n2 = n3 % 100; // last two digits
        int n1 = n3 / 100; // first digit (hundreds)
        if (spelled > 0 && spelled < 100)
            spellingStack.push(spell(Keyword.AND)); // add "and" between
        if (groupIdx > 0)
            spellingStack.push(groups[groupIdx]); // push the name of the group (thousands, millions)
        if (n1 == 0 || n2 != 0)
            spellN2(n2, spellingStack); // spell last two digits
        if (n1 > 0) {
            if (n2 > 0 && useHundredsAnd())
                spellingStack.push(spell(Keyword.AND)); // add "and" between hundreds and the last 2-digits if configured
            spellingStack.push(spell(Keyword.HUNDRED));
            spellingStack.push(cardinals.get(n1));
        }
    }

    public ENConverter() throws IOException {
        loadCardinals(); // make sure cardinal number map is loaded
    }

    /**
     * Spell a 2-digit number on the spelling stack
     *
     * @param n2            2-digit number
     * @param spellingStack stack containing spelled words
     */
    private void spellN2(int n2, Stack<String> spellingStack) {
        if (n2 < 0 || n2 >= 100)
            throw new IllegalArgumentException("The number must belong to the 0 .. 99 interval");
        String spelled = cardinals.get(n2);
        if (spelled == null) {
            int tens = (n2 / 10) * 10, units = n2 % 10;
            spelled = cardinals.get(tens) + (units > 0 ? unitTensSeparator() + cardinals.get(units) : "");
        }
        spellingStack.push(spelled);
    }
}
